/*
 * Created on 07.03.2009
 *
 * To change the template for this generated file go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
package org.higginsexamples.mailboxserver;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.osgi.service.http.HttpContext;
import org.pf.six.XmlStreamReader;
import org.pf.text.StringUtil;
import org.pf.util.Base64Converter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

public class BasicSecurityHttpContext implements HttpContext
{
	protected static final StringUtil SU = StringUtil.current() ;
	
	private static final String SEC_COOKIE_NAME = "SECSESSION" ;

	private static final String[] REQUIRED_CLAIMS = {
		SecurityConstants.XMLSOAP_EMAIL
	};
	
	private static final String[] OPTIONAL_CLAIMS = {
		SecurityConstants.XMLSOAP_FIRSTNAME,
		SecurityConstants.XMLSOAP_LASTNAME
	};

	private static final XPath XPATH = createXPath();
	// "/token/attributes/attribute[@name='email']"
	private static final XPathExpression XPE_EMAIL = compileExpression( "/" + SecurityConstants.TAG_TOKEN + "/" + SecurityConstants.TAG_ATTRIBUTES 
			+ "/" + SecurityConstants.TAG_ATTRIBUTE + "[@" + SecurityConstants.ATTR_NAME + "='" + SecurityConstants.ATTRNAME_EMAIL + "']"  );

	private static XPath createXPath() 
	{
		XPathFactory xpathFactory;

		xpathFactory = XPathFactory.newInstance();
		return xpathFactory.newXPath();
	}

	private static XPathExpression compileExpression( String expression )
	{
		XPathExpression xpathExpression;
		try
		{
			xpathExpression = XPATH.compile( expression );
		}
		catch ( XPathExpressionException e )
		{
			xpathExpression = null;
			System.err.println( "Failed to parse " + expression );
			e.printStackTrace();
		}
		return xpathExpression;
	}

	
	public String getMimeType( String name )
	{
		// TODO Auto-generated method stub
		return null;
	}

	public URL getResource( String name )
	{
		// TODO Auto-generated method stub
		return null;
	}

	public boolean handleSecurity( HttpServletRequest request, HttpServletResponse response ) throws IOException
	{
		Cookie cookie ;
		String authHeader ;
		
		// First check if the security session cookie is available
		cookie = this.extractCookie( request, SEC_COOKIE_NAME ) ;
		if ( cookie != null )
		{
			return this.handleSecuritySessionCookie( request, response, cookie ) ;
		}
		// Second check if the authentication header is available
		authHeader = this.extractHeader( request, SecurityConstants.AUTH_HEADER_NAME ) ;
		if ( authHeader != null )
		{
			return this.handleAuthenticationHeader( request, response, authHeader ) ;
		}
		this.createChallengeResponse( request, response ) ;
		return false;
	}

	protected void createChallengeResponse( HttpServletRequest request, HttpServletResponse response )
	{
		StringBuffer challenge = new StringBuffer(400);
		
		challenge.append( SecurityConstants.AUTH_TYPE ) ;
		challenge.append( " " ) ;
		challenge.append( SecurityConstants.REQUIRED ) ;
		challenge.append( "=" ) ;
		challenge.append( this.getRequiredClaims() ) ;
		challenge.append( "," ) ;
		challenge.append( SecurityConstants.OPTIONAL ) ;
		challenge.append( "=" ) ;
		challenge.append( this.getOptionalClaims() ) ;		
		response.setHeader( SecurityConstants.AUTH_CHALLENGE_NAME, challenge.toString() ) ;
		response.setStatus( HttpServletResponse.SC_UNAUTHORIZED ) ; // 401
		
		// log to console
		System.out.println("Challenge: ");
		System.out.println(challenge.toString());

	}

	protected String getRequiredClaims()
	{
		return this.encodeClaims( REQUIRED_CLAIMS ) ;
	}

	protected String getOptionalClaims()
	{
		return this.encodeClaims( OPTIONAL_CLAIMS ) ;
	}
	
	protected String encodeClaims( String[] claims )
	{
		String claimsStr ;
		
		claimsStr = SU.asString( claims, SecurityConstants.CLAIM_SEPARATOR ) ; // URLs separated by blanks
		return Base64Converter.encode( claimsStr ) ;
	}
	
	protected boolean handleSecuritySessionCookie( HttpServletRequest request, HttpServletResponse response, Cookie cookie ) 
	{
		return true ;
	} // handleSecuritySessionCookie()
	
	protected boolean handleAuthenticationHeader( HttpServletRequest request, HttpServletResponse response, String authHeader ) 
	{
		Cookie cookie ;
		
		System.out.println( "Received authentication: " + authHeader );
		cookie = new Cookie( SEC_COOKIE_NAME, authHeader ) ;
		response.addCookie( cookie ) ;
		this.extractDataFromToken( request, authHeader ) ;
		return true ;
	} // handleSecuritySessionCookie()
	
	/**
	 * Extracts the relevant data from the given token and puts it to a request attribute.
	 * @param token Base64 encoded XML token 
	 */
	protected void extractDataFromToken( HttpServletRequest request, String token )
	{
		String xmlToken ;
		ByteArrayInputStream inputStream;
		Document document;
		String value ;
		
		xmlToken = Base64Converter.decodeToString( token ) ;
		System.out.println( "decoded: " + xmlToken );
		inputStream = new ByteArrayInputStream( xmlToken.getBytes() );
		try
		{
			document = XmlStreamReader.loadDOM( inputStream, null, false ) ;
		}
		catch ( Exception ex )
		{
			ex.printStackTrace();
			return ;
		}
		value = this.evaluate( document, XPE_EMAIL ) ;
		if ( value != null )
		{
			request.setAttribute( CommonConstants.MAILBOXES, value ) ;
		}
	}

	/**
	 * Returns the value of the first request header with the given name. 
	 */
	protected String extractHeader( HttpServletRequest request, String headerName ) 
	{
		return request.getHeader( headerName ) ;
	} // extractHeader()
	
	protected Cookie extractCookie( HttpServletRequest request, String cookieName ) 
	{
		Cookie[] cookies ;
		
		cookies = request.getCookies() ;
		if ( cookies == null )
		{
			return null ;
		}
		for (int i = 0; i < cookies.length; i++ )
		{
			if ( cookieName.equalsIgnoreCase( cookies[i].getName() ) )
			{
				return cookies[i] ;
			}
		}
		return null ;
	} // extractCookie()

	protected Document parse( String xml )
	{
		DocumentBuilderFactory factory;
		DocumentBuilder documentBuilder;
		ByteArrayInputStream inputStream;
		Document document;

		factory = DocumentBuilderFactory.newInstance();
		try
		{
			documentBuilder = factory.newDocumentBuilder();
		}
		catch ( ParserConfigurationException e )
		{
			System.err.println( "Failed to create a DocumentBuilder." );
			e.printStackTrace();
			return null;
		}
		inputStream = new ByteArrayInputStream( xml.getBytes() );
		try
		{
			document = documentBuilder.parse( inputStream );
			return document ; 
		}
		catch ( Exception e )
		{
			System.err.println( "Failed to parse validation response: " + xml );
			e.printStackTrace();
			return null;
		}
	}
	
	protected String evaluate( Node node, XPathExpression expression )
	{
		String value ;
		
		try
		{
			value = expression.evaluate( node ) ;
		}
		catch ( XPathExpressionException e )
		{
			System.err.println( "Failed to extract " + expression.toString() + " from validation response. " ) ;
			e.printStackTrace();
			return null ;
		}
		if ( value == null )
		{
			return null ;
		}
		return value.trim() ;
	}
	
}
